﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Kokomo.PeInspector.Reflection.Disassembly;
using Kokomo.Mirror.CliMetadata;

namespace Kokomo.PeInspector.Reflection
{
	/// <summary>
	/// Provides functionality to assist with creating a dependency graph.
	/// </summary>
	/// <seealso cref="DependencyGraph"/>
	public class DependencyGraphHelper
	{
		/// <summary>
		/// Initializes a new instance of <see cref="DependencyGraphHelper"/>.
		/// </summary>
		/// <param name="host"></param>
		public DependencyGraphHelper(
			IAssemblyListHost host
			)
		{
			if (host == null) throw new ArgumentNullException("host");
			this.Host = host;
		}

		public IAssemblyListHost Host { get; private set; }

		protected IEnumerable<MemberInfo> GetDependentMembers<TDependency>(
			Assembly sourceAssembly,
			ModuleMemberReference dependencyMember,
			Func<TDependency, bool> dependencyFilter,
			ProgressInfo progress
			)
			where TDependency : MsilMemberDependency
		{
			if (sourceAssembly == null) throw new ArgumentNullException("sourceAssembly");
			if (ModuleMemberReference.IsEmpty(dependencyMember)) throw new ArgumentNullException("dependencyMember");

			IList<Assembly> assemblyList = this.Host.AssemblyList.Assemblies;
			if (progress != null) progress.Maximum = assemblyList.Count;
			List<MemberInfo> dependents = new List<MemberInfo>();

			DependencyIndex index = this.Host.DependencyIndex;

			IEnumerable<IGrouping<ModuleMemberReference, MsilMemberDependency>> dependencyGroups = index.GetDependentsOf(dependencyMember, progress);

			foreach (var dependencyGroup in dependencyGroups)
			{
				if (
					(dependencyFilter == null)
					|| dependencyGroup.OfType<TDependency>().Any(dependencyFilter)
					)
				{
					MemberInfo member = dependencyGroup.Key.LoadMember();
					dependents.Add(member);

				}
			}

			return dependents;
		}
		//protected IEnumerable<MemberInfo> GetDependentTypes(
		//    Func<Type, bool> dependencyFilter,
		//    ProgressInfo progress
		//    )
		//{
		//    List<MemberInfo> dependants = new List<MemberInfo>();

		//    IList<Assembly> assemblyList = this.Host.AssemblyList;
		//    progress.Maximum = assemblyList.Count;
		//    for (int i = 0; i < assemblyList.Count; i++)
		//    {
		//        progress.Progress = i + 1;
		//        Assembly assembly = assemblyList[i];
		//        AssemblyIndex index = this.Host.GetAssemblyIndex(assembly);

		//        Type[] types = index.Types;
		//        foreach (var type in types)
		//        {
		//            if (dependencyFilter(type))
		//                dependants.Add(type);
		//        }
		//    }

		//    return dependants;
		//}

		#region Type graphs
		public IEnumerable<MemberInfo> GetDerivedDependencies(MemberInfo member, ProgressInfo progress)
		{
			Type baseType = member as Type;
			if (baseType == null) return null;

			var derivedTypes = this.GetDependentMembers<MsilTypeDependency>(
				baseType.Module.Assembly,
				new ModuleMemberReference(baseType),
				d => (d.Usage == MsilTypeUsage.Extends),
				progress
				);
			return derivedTypes;
		}
		public IEnumerable<MemberInfo> GetExtensionDependencies(MemberInfo member, ProgressInfo progress)
		{
			Type baseType = member as Type;
			if (baseType == null) return null;

			var derivedTypes = this.GetDependentMembers<MsilTypeDependency>(
				baseType.Module.Assembly,
				new ModuleMemberReference(baseType),
				d => (d.Usage == MsilTypeUsage.Extension),
				progress
				);
			return derivedTypes;
		}
		public IEnumerable<MemberInfo> GetAttributeDependencies(MemberInfo member, ProgressInfo progress)
		{
			Type baseType = member as Type;
			if (baseType == null) return null;

			var attributedMembers = this.GetDependentMembers<MsilTypeDependency>(
				baseType.Module.Assembly,
				new ModuleMemberReference(baseType),
				d => (d.Usage == MsilTypeUsage.Attribute),
				progress
				);
			return attributedMembers;
		}
		public IEnumerable<MemberInfo> GetThrownDependencies(MemberInfo member, ProgressInfo progress)
		{
			Type baseType = member as Type;
			if (baseType == null) return null;

			var throwingMembers = this.GetDependentMembers<MsilTypeDependency>(
				baseType.Module.Assembly,
				new ModuleMemberReference(baseType),
				d => (d.Usage == MsilTypeUsage.Thrown),
				progress
				);
			return throwingMembers;
		}
		public IEnumerable<MemberInfo> GetImplementingDependencies(MemberInfo member, ProgressInfo progress)
		{
			Type baseType = member as Type;
			if (baseType == null) return null;

			var dependents = this.GetDependentMembers<MsilTypeDependency>(
				baseType.Module.Assembly,
				new ModuleMemberReference(baseType),
				d => (d.Usage == MsilTypeUsage.Implements),
				progress
				);
			return dependents;
		}
		public IEnumerable<MemberInfo> GetInstantiationDependencies(MemberInfo member, ProgressInfo progress)
		{
			Type instantiatedType = member as Type;
			if (instantiatedType == null) return null;

			var dependents = this.GetDependentMembers<MsilTypeDependency>(
				instantiatedType.Module.Assembly,
				new ModuleMemberReference(instantiatedType),
				d => (d.Usage == MsilTypeUsage.Instantiation),
				progress
				);
			return dependents;
		}
		public IEnumerable<MemberInfo> GetReferenceDependencies(MemberInfo member, ProgressInfo progress)
		{
			Type instantiatedType = member as Type;
			if (instantiatedType == null) return null;

			return this.GetDependentMembers<MsilTypeDependency>(
				instantiatedType.Assembly,
				new ModuleMemberReference(instantiatedType),
				null,
				progress
				);
		}
		public IEnumerable<MemberInfo> GetReturnedByDependencies(MemberInfo member, ProgressInfo progress)
		{
			Type instantiatedType = member as Type;
			if (instantiatedType == null) return null;

			return this.GetDependentMembers<MsilTypeDependency>(
				instantiatedType.Assembly,
				new ModuleMemberReference(instantiatedType),
				d => (d.Usage == MsilTypeUsage.ReturnValue),
				progress
				);
		}
		public IEnumerable<MemberInfo> GetAcceptedByDependencies(MemberInfo member, ProgressInfo progress)
		{
			Type instantiatedType = member as Type;
			if (instantiatedType == null) return null;

			return this.GetDependentMembers<MsilTypeDependency>(
				instantiatedType.Assembly,
				new ModuleMemberReference(instantiatedType),
				d => (d.Usage == MsilTypeUsage.ParameterDeclaration),
				progress
				);
		}
		#endregion
		#region Field graphs
		public IEnumerable<MemberInfo> GetSetterDependencies(MemberInfo member, ProgressInfo progress)
		{
			FieldInfo field = member as FieldInfo;
			if (field == null) return null;

			return this.GetDependentMembers<MsilFieldDependency>(
				field.Module.Assembly,
				new ModuleMemberReference(field),
				dependency => (dependency.Usage != MsilVariableReferenceType.Load),
				progress
				);
		}
		public IEnumerable<MemberInfo> GetUsedDependencies(MemberInfo member, ProgressInfo progress)
		{
			FieldInfo field = member as FieldInfo;
			if (field == null) return null;

			return this.GetDependentMembers<MsilFieldDependency>(
				field.Module.Assembly,
				new ModuleMemberReference(field),
				dependency => (dependency.Usage == MsilVariableReferenceType.Load),
				progress
				);
		}
		#endregion
		#region Method graphs
		#region Call Graph
		public IEnumerable<MemberInfo> GetCallDependencies(MemberInfo member, ProgressInfo progress)
		{
			MethodBase method = member as MethodBase;
			if (method == null) return null;

			DependencyIndex index = this.Host.DependencyIndex;
			IEnumerable<MsilDependency> dependencies = index.GetDependenciesOf(new ModuleMemberReference(method), progress);
			IEnumerable<MemberInfo> dependentMembers = dependencies.OfType<MsilMethodDependency>().Where(m => m.Usage == MsilMethodUsage.Invoke).Select(m => m.GetMemberDependency());

			return dependentMembers;
		}
		#endregion
		#region Caller Graph

		public IEnumerable<MemberInfo> GetCallerDependencies(MemberInfo member, ProgressInfo progress)
		{
			MethodBase calledMethod = member as MethodBase;
			if (calledMethod == null) return null;

			List<MemberInfo> methods = new List<MemberInfo>();
			methods.Add(calledMethod);

			if (
				(calledMethod.DeclaringType != null)
				&& (calledMethod.DeclaringType.IsInterface)
				)
			{
				methods.AddRange(GetImplementations(calledMethod, null));
			}
			else
			{
				MethodInfo baseMethod = null;
				{
					MethodInfo methodInfo = calledMethod as MethodInfo;
					if (methodInfo != null)
						baseMethod = methodInfo.GetBaseDefinition();
				}

				if (
					(baseMethod != null)
					&& (baseMethod != calledMethod)
					)
				{
					methods.Add(baseMethod);
				}
			}

			List<MemberInfo> dependents = new List<MemberInfo>();
			foreach (var method in methods)
			{
				dependents.AddRange(this.GetDependentMembers<MsilMethodDependency>(
					member.Module.Assembly,
					new ModuleMemberReference(method),
					d => (d.Usage == MsilMethodUsage.Invoke),
					progress
					));
			}

			return dependents;
		}
		#endregion
		#region Implementation Graph
		public IEnumerable<MemberInfo> GetImplementations(MemberInfo member, ProgressInfo progress)
		{
			MethodInfo interfaceMethod = member as MethodInfo;
			if (
				(interfaceMethod == null)
				|| (interfaceMethod.DeclaringType == null)
				|| (!interfaceMethod.DeclaringType.IsInterface)
				)
				return null;

			var implementations = this.GetImplementingDependencies(member.DeclaringType, progress)
				.Where(t => !((Type)t).IsInterface)
				.Select(t => interfaceMethod.GetImplementationOn((Type)t)).ToArray();
			return implementations;
		}
		#endregion
		#endregion
	}
}
